<template>
<div class="focus-box" :class="{ 'mouse-disable': disable, 'mg-select-tree-small': size === 'small', 'mg-select-tree-mini': size === 'mini' }">
  <div class="row no-wrap items-end" :class="{ 'btn-disable': disable }">
    <div class="col row no-wrap mg-tree-input-chips">
        <!-- 根据multipleLimit控制显示个数 -->
        <template v-for="(item, index) in selectList">
          <div v-if="!(multipleLimit && index > multipleLimit - 1)" :key="`select_${index}`" class="focus-item row items-center">
            <span :title="item[nodeLabel]">{{ item['showLabel'] }}</span>
            <span class="inline-block cursor-pointer focus-delete non-selectable"
                  @click.stop="unfocus(item)">x</span>
          </div>
        </template>
        <span v-if="isMultiTagShow" class="mg-tree-input-chips-tag-text non-selectable">+ {{multiTagNumber}}</span>
        <q-input v-model="inputText"
                 class="col mg-tree-auto-input no-padding"
                 style="width: 100px"
                 :readonly="inputDisable"
                 :placeholder="placeholder">
          <q-autocomplete :filter="filterList"
                          :debounce="300"
                          :min-characters="0"
                          :max-results="maxResults"
                          @selected="selected"
                          :static-data="{ field: nodeKey, list }" />
        </q-input>
    </div>
    <div>
      <span class="inline-block cursor-pointer focus-add" @click.self.stop="openTree">+</span>
      <!-- 下拉框显示树结构 -->
      <q-popover v-if="modeNum === 1" v-model="showPopover" fit :max-height="popHeight">
        <!-- v-if="showPopover" 便于打开下拉框时触发计算树结构 -->
        <component :is="componentName"
                   :data="tree"
                   :deep="deep"
                   v-if="showPopover"
                   :node-key="nodeKey"
                   :node-label="nodeLabel"
                   :init-ticked="tickedList"
                   :tick-change="tickChange"></component>
      </q-popover>
    </div>
  </div>
  <mg-modal v-if="modeNum === 2"
            height="80vh"
            width="60vw"
            :title="modalTitle"
            title-align="left"
            :open.sync="showDialog"
            :no-backdrop-dismiss="true">
    <component :is="componentName"
               slot="body"
               :data="tree"
               :deep="deep"
               v-if="showDialog"
               :init-ticked="tickedList"
               :node-key="nodeKey"
               :node-label="nodeLabel"
               :tick-change="tickChange"></component>
  </mg-modal>
</div>
</template>

<script>
import { Dialog, QPopover, QInput, QAutocomplete } from 'quasar'
import CheckboxTree from './mg-select-tree/checkbox-tree'
import RadioTree from './mg-select-tree/radio-tree'
import { updateNode, parseTree, getLeafByKey } from './mg-select-tree/util'
export default {
  name: 'MgSelectTree',
  components: {
    QPopover,
    QInput,
    QAutocomplete,
    'checkbox-tree': CheckboxTree,
    'radio-tree': RadioTree
  },
  props: {
    fit: { // 是否自适应popover位置
      type: Boolean,
      default: false
    },
    fitRatio: { // 自适应比例
      type: Number,
      default: 0.5
    },
    size: { // 尺寸, small | mini
      type: String,
      default: 'small'
    },
    deep: { // 勾选策略是否同时勾选当前节点的所有子节点
      type: Boolean,
      default: false
    },
    placeholder: {
      type: String,
      default: '请输入'
    },
    tree: { // 树结构数据
      type: Array,
      default: () => []
    },
    list: { // 默认部门 一维数组 [{label, code}]
      type: Array,
      default: () => [],
      required: false
    },
    focusList: { // 初始选中的节点的code集合(用于1.初始化; 2.与最先选中项比较是否有变化)
      type: Array,
      default: () => [],
      required: false
    },
    wordSize: { // 每项最多显示字数
      type: [Number, String],
      default: 3
    },
    mode: { // 选择树结构显示方式
      type: [Number, String],
      default: 1 // 1 下拉框;  2 弹框
    },
    modalTitle: { // 弹出框标题
      type: String,
      default: '选择树结构'
    },
    disable: { // 组件禁用
      type: Boolean,
      default: false
    },
    nodeKey: { // 节点唯一标识名
      type: String,
      default: 'code'
    },
    nodeLabel: { // 数据中的显示字段名
      type: String,
      default: 'label'
    },
    fullLabel: { // 是否显示整个label（label + 层级 )
      type: Boolean,
      default: false
    },
    multipleLimit: { // 控制最多显示条数, 0 为不限制
      type: Number,
      default: 0
    },
    delDialog: { // 删除数据时提示
      type: Boolean,
      default: false
    },
    single: { // 是否为单选
      type: Boolean,
      default: false
    },
    inputDisable: { // 是否禁止输入（true  只能打开树结构勾选，不可输入选择）
      type: Boolean,
      default: false
    }
  },
  data () {
    return {
      maxResults: 100, // 显示匹配项的最大个数
      inputText: '', // 显示的名字
      selectList: [], // 当前已选,一维数组
      tickedList: [], // 选中的节点, 记录每次操作后的code
      showPopover: false, // 是否显示下拉框
      popHeight: '0px',
      showDialog: false, // 是否显示弹出框
      linkLabel: {} // 当前节点链式数据
    }
  },
  computed: {
    componentName () { // 当前树组件
      return this.single ? 'radio-tree' : 'checkbox-tree'
    },
    wordSizeNum () { // WordSize转换为数字
      return Number(this.wordSize)
    },
    modeNum () { // mode转换为数字
      return Number(this.mode)
    },
    isMultiTagShow () { // 是否显示扩展的数量: 定义了最大显示个数 且 当前选中个数大于定义值
        return this.multipleLimit && this.selectList.length > this.multipleLimit
    },
    multiTagNumber () { // 显示值
        return this.selectList.length - this.multipleLimit
    }
  },
  watch: {
    showPopover (value) {
      value && this.calcPopover()
    },
    // tree (value) { // 若同时监听tree和focusList, 推荐启用节流方式
    //   // 数据改变时重置选中值
    //   this.fullLabel && this.getLinkLabel(value)
    //   this.parse(value, JSON.parse(JSON.stringify(this.focusList)))
    // },
    // 监听场景: 1. focusList = [code]; 2. focusList.splice(0, 2[, code]); 3. focusList.push(code)
    // 无法监听的情况: focusList[index] = code
    focusList: {
      immediate: true,
      handler (value) {
        // 数据改变时重置选中值
        this.fullLabel && this.getLinkLabel(this.tree)
        this.parse(this.tree, JSON.parse(JSON.stringify(value)))
      }
    }
  },
  methods: {
    calcPopover () { // 计算popover高度
      const containerH = document.body.clientHeight // 视口高度
      let maxH = containerH * this.fitRatio // popover最大高度
      if (this.fit) {
        this.$nextTick(() => {
          const addBtn = this.$el.querySelector('.focus-add')
          const locate = addBtn.getBoundingClientRect()
          const {top, height} = locate
          const bottom = containerH - top - height // 组件距离视口底部高度
          if (bottom < maxH && top < maxH) {
            maxH = Math.max(top, bottom)
          }
          this.setPopoverHeight(maxH)
        })
      } else {
        this.setPopoverHeight(maxH)
      }
    },
    setPopoverHeight (maxH) {
      maxH = Math.floor(maxH)
      parseFloat(this.popHeight) !== maxH && (this.popHeight = `${maxH}px`)
    },
    selected (item, keyboard) { // 输入匹配后选中一项

      if (keyboard) { // 禁止键盘上下键选中
        return
      }
      this.inputText = ''
      // 已存在的, 忽略
      if (this.selectList.some(_ => _[this.nodeKey] === item[this.nodeKey])) {
        return
      }

      // 更新树的选中状态,并显示
      const targetNode = getLeafByKey(item[this.nodeKey], this.nodeKey, this.tree)
      if (targetNode) {
        targetNode['checked'] = true
        // 单选树，则替换原有内容; 多选树，则直接push
        if (this.single) {// 单选树，则替换原有内容
          this.tickedList.length !== 1 && (this.tickedList.length = 1)
          this.tickedList[0] = targetNode[this.nodeKey]
        } else {
          this.tickedList.push(targetNode[this.nodeKey])
        }
        this.setSelectList(this.tickedList)
      }
    },
    filterList (terms, { list }) { // 输入匹配(输入为空时不匹配)
      terms = terms.trim()
      let value = list.filter(_ => terms && _[this.nodeLabel].includes(terms))
      return this.fullLabel ? value.map(v => ({
        ...v,
        [this.nodeLabel]: this.linkLabel[v[this.nodeKey]] || v[this.nodeLabel]
      })) : value
    },
    openTree () { // 显示树结构
      if (this.modeNum === 2) {
        this.showDialog = true
      } else {
        this.showPopover = true
      }
    },
    parse (data = [], focusList = []) {
      // 单选树，则只有第一个元素有效
      this.single && (focusList = focusList.slice(0, 1))
      // 数据变更,需清空历史操作
      this.tickedList = focusList || []
      this.selectList = []
      // 将选中节点合并到tree的checked属性中
      focusList.length && parseTree(data, focusList, this.nodeKey)
      this.setSelectList(this.focusList)
    },
    getSelectList () { // 获取选中的一维数组,父组件通过ref调用
      const newList = this.selectList.map(_ => _[this.nodeKey])
      // 选中是否无变化
      const unchanged = newList.join('') === this.focusList.join('')
      return unchanged ? false : newList
    },
    getSelectTree () { // 获取整个树结构,父组件通过ref调用
      return this.tree
    },
    setSelectList (tickedList) {
      let result = []
      const selectedList = this.recursiveNode(this.tree) || []
      const nodeKey = this.nodeKey
      tickedList.forEach((_, i) => {
        const temp = selectedList.find(o => o[nodeKey] === _)
        temp && result.push(temp)
      })
      this.selectList = result
      // 选中项变化, 触发事件
      this.$emit('select-change', result.map(_ => _[nodeKey]))
    },
    // 获取所有已选节点,组成一维数组,赋值给this.selectList
    // codes中存储已push的节点code, 做去重判断
    recursiveNode (list = [], codes = [], result = []) {
      for (let i = 0; i < list.length; i++) {
        const item = list[i]
        // 若当前节点已选中,则记录label并跳过其子节点
        if (item.checked && !codes.includes(item[this.nodeKey])) {
          codes.push(item[this.nodeKey])
          result.push(this.getWrapNode(item))
        }
        if (Array.isArray(item.children) && item.children.length) {
          result = this.recursiveNode(item.children, codes, result)
        }
      }
      return result
    },
    getWrapNode (item) { // 获取节点信息
      let label = this.fullLabel ? this.linkLabel[item[this.nodeKey]] || item[this.nodeLabel] : item[this.nodeLabel]
      const showLabel = label.length > this.wordSizeNum
        ? (label.substr(0, this.wordSizeNum) + '...')
        : label
      return {
        [this.nodeKey]: item[this.nodeKey],
        [this.nodeLabel]: label,
        showLabel,
        checked: item['checked']
      }
    },
    unfocus (item) { // 从显示列表删除一项数据时触发
      const code = item[this.nodeKey]
      const label = item[this.nodeLabel]
      if (!code) {
        return
      }

      this.delDialog
          ? Dialog.create({
              message: `是否删除 "${label}" ？`,
              ok: '确认',
              cancel: '取消',
              color: 'secondary'
          }).then(function () {
                this.delHandler(item)
          }.bind(this)).catch(() => {})// catch代替 点击取消按钮时 的报错
        : this.delHandler(item)
    },
    delHandler (item) {
        // 更新树结构
        const nodeCodes = [item[this.nodeKey]]
        this.tree.forEach(_ => updateNode(nodeCodes, false, _, this.nodeKey))
        // 更新当前显示内容
        this.tickedList.splice(this.tickedList.indexOf(item[this.nodeKey]), 1)
        this.setSelectList(this.tickedList)
    },
    removeMultipleItem (codeList = []) { // 取消已勾选的多个节点,父组件通过ref调用
      this.tree.forEach(_ => updateNode(codeList, false, _, this.nodeKey))
      // 更新当前显示内容
      const tickedList = JSON.parse(JSON.stringify(this.tickedList || []))
      codeList.forEach(code => tickedList.splice(tickedList.indexOf(code), 1))
      this.tickedList = tickedList
      this.setSelectList(this.tickedList)
    },
    tickChange (tickedList = []) { // mg-tree勾选或去勾选操作后
      tickedList = Array.from(new Set(tickedList)) // 先去重
      const isSame = this.tickedList.join('') === tickedList.join('')
      if (isSame) {
        return
      }
      this.tickedList = tickedList
      this.setSelectList(this.tickedList)
    },
    getLinkLabel (list, parentLabel = '') { // 保存当前的链式数据
      list.forEach(v => {
        let children = v.children
        let label = v[this.nodeLabel]
        let code = v[this.nodeKey]
        let parent = parentLabel ? `${parentLabel}/${label}` : label
        // 判断是否有重复code 重复不加parent
        this.linkLabel[code] = this.linkLabel[code] ? label : parent
        // 循环处理
        children && children.length && this.getLinkLabel(children, parent)
      })
    }
  }
}
</script>

<style lang="stylus" scoped>
$size = 16px
$size-btn = 32px
$size-btn-mini = 28px
$border-base = #DCDFE6
$placeholder = #C0C4CC
$text-color = #333
$text-regular = #606266

.focus-box
  padding 0
  font-size 12px
  .focus-item
    background-color #f2f3f4
    padding 0 5px
    margin 4px 0 4px 6px
    font-weight 500
    border-radius 4px
    border-width 1px
    border-style solid
    border-color #e5e6e9
    color $text-color
    .focus-delete
      width $size
      height $size
      line-height $size
      text-align center
      border-radius $size
      color #7b848f
      background-color $placeholder
      transform scale(.8)
      &:hover
        background-color #7b848f
        color #FFF
  .focus-add
    width $size-btn
    height $size-btn
    line-height $size-btn
    border-radius 0 4px 4px 0
    border 1px solid $border-base
    border-left none
    text-align center
    color $text-regular
    background-color #f2f3f4
    font-size 16px
  &.mg-select-tree-mini
    .focus-add
      width $size-btn-mini
      height $size-btn-mini
      line-height $size-btn-mini
/* 鼠标禁用样式 */
.mouse-disable
  cursor not-allowed
  .btn-disable
    pointer-events none
    opacity .6
</style>
<style lang="stylus">
$border-base = #DCDFE6
$text-color = #333
$placeholder = #C0C4CC
$border-lighter = #F5F7FA

$input-small-height = 26px
$input-mini-height = 22px

.mg-tree-auto-input
  &.q-if:before, &.q-if:after
    height 0
    border none !important
  input.col.q-input-target.q-no-input-spinner
    font-size 12px !important
    padding-left 4px
.mg-tree-auto-input.q-if-has-content
  padding 3px !important
.mg-tree-input-chips
  border-radius 4px 0 0 4px
  border-width 1px
  border-style solid
  border-color $border-base
  color $text-color
  transition border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1)
  &:hover
    border-color $placeholder
  .mg-tree-input-chips-tag-text
    display inline-block
    background-color #f4f4f5
    margin 4px 0 4px 6px
    padding 0 8px
    border-radius 4px
    border-width 1px
    border-style solid
    border-color #e9e9eb
    line-height $input-small-height - 4
    height $input-small-height - 4
    color $text-color
.focus-box.mg-select-tree-mini
  .q-if .q-if-inner
    min-height $input-mini-height
  .mg-tree-auto-input
    .q-if-baseline
      line-height $input-mini-height
    input.col.q-input-target.q-no-input-spinner
      height $input-mini-height
  .mg-tree-auto-input.q-if-has-content
    padding 2px !important
  .mg-tree-input-chips
    .mg-tree-input-chips-tag-text
      line-height $input-mini-height - 4
      height $input-mini-height - 4

/* quasar tree组件 */
.mg-tree
  &.q-tree
    color $text-regular
  .q-tree-node
    padding 0 0 2px 18px !important
  .q-tree-node:after,
  .q-tree-node-header:before
    border-left none !important
  .q-tree-node-header:before
    border-bottom none !important
  .q-tree-node-header
    padding 1px !important
  .q-tree-node-parent
    padding-left 0px !important
  .q-tree-children
    padding-left 22px !important
  /*单选树*/
  &.mg-tree-single.q-tree
    .q-tree-children
      padding-left 15px !important
/* quasar input组件 */
.mg-tree-search-input
  &.q-if:before,
  &.q-if:after
    border-style solid
    border-radius 4px
  &.q-if-focused:after
    border-width 1px
  &.q-if-standard
    padding-top 3px
    padding-bottom 3px
  &.q-if:not(.q-if-disabled):not(.q-if-error):not(.q-if-warning):hover:before,
  &.q-if.q-if-readonly:not(.q-if-error):not(.q-if-warning):after
    color #bdbdbd
  &.q-input
    input.col.q-input-target.q-no-input-spinner
      padding-left 8px
</style>
